package com.linyu.dislockluaredisboot.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀控制器
 *
 * @author 23087
 */
@RestController
public class SeckillController {

    // 分布式锁的 key
    public static final String REDIS_LOCK = "redis_lock";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    /**
     * 使用自定义配置的 jedisPool 来执行 lua 脚本
     */
    @Resource
    private JedisPool jedisPool;

    /**
     * 使用 redisson 实现分布式锁
     */
    @Resource
    private Redisson redisson;

    @GetMapping("/sk6")
    public String seckillHandler6(){

        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        RLock rLock = redisson.getLock(REDIS_LOCK);
        try {
            // 添加分布式锁
            Boolean lockOk = rLock.tryLock(); // 尝试加锁
            if (Boolean.FALSE.equals(lockOk)){
                return "没有抢到锁";
            }
            // 添加锁成功
            // 从 redis 中获取库存
            String stock = operations.get("sk:0008");
            int mount = stock == null ? 0 : Integer.parseInt(stock);
            if (mount > 0){
                // 修改库存后再写会 redis
                operations.set("sk:0008", String.valueOf(--mount));
                return "秒杀成功，库存剩余：" + mount + "台";
            }
        }finally {
            rLock.unlock();
        }
        return "抱歉，您没有抢到";
    }

    /**
     * 采用 lua 脚本实现分布式锁
     * lua 在 redis 的执行具有原子性
     * @return
     */
    @GetMapping("/sk5")
    public String seckillHandler5(){
        // 为每一个访问的客户端随机生成一个客户端唯一标识
        String clientId = UUID.randomUUID().toString();

        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        try {
            // 添加锁,同时添加过期时间
            // 将锁的 value 设置为当前客户端唯一标识 clientId
            Boolean lockOk = operations.setIfAbsent(REDIS_LOCK, clientId, 30, TimeUnit.SECONDS);
            if (Boolean.FALSE.equals(lockOk)){
                return "没有抢到锁";
            }
            // 添加锁成功
            // 从 redis 中获取库存
            String stock = operations.get("sk:0008");
            int mount = stock == null ? 0 : Integer.parseInt(stock);
            if (mount > 0){
                // 修改库存后再写会 redis
                operations.set("sk:0008", String.valueOf(--mount));
                return "秒杀成功，库存剩余：" + mount + "台";
            }
        }finally {
            // todo 锁过期时间续约，锁续命(此处未实现)
            // redis.call() 是 lua 中对 Redis 命令的调用函数
            try(Jedis jedis = jedisPool.getResource()) {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                        "then return redis.call('del', KEYS[1]) " +
                        "end " +
                        "return 0";
                // eval() 方法的返回值为脚本 script 的返回值
                Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(clientId));
                // 当前的 eval 结果 ---> 1： 删除成功， 0： 删除失败
                if ("1".equals(eval)) {
                    System.out.println("释放锁成功");
                }else {
                    System.out.println("释放锁时发生异常");
                }
            }
        }
        return "抱歉，您没有抢到";
    }

    /**
     * 弥补分布式锁代码的，局限性 ---> 原代码可能会导致删除其它人加的锁
     * todo 这代码在高并发下也会产生删其它人锁的问题 --->
     *  比如：在 finally 代码中，如果当前服务刚好判断了锁是自己的，
     *  但是自己的锁刚好过期了，此刻其它服务进来加锁，然后当前服务将其它服务加的锁删掉了
     * @return
     */
    @GetMapping("/sk4")
    public String seckillHandler4(){
        // 为每一个访问的客户端随机生成一个客户端唯一标识
        String clientId = UUID.randomUUID().toString();

        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        try {
            // 添加锁,同时添加过期时间
            // 将锁的 value 设置为当前客户端唯一标识 clientId
            Boolean lockOk = operations.setIfAbsent(REDIS_LOCK, clientId, 30, TimeUnit.SECONDS);
            if (Boolean.FALSE.equals(lockOk)){
                return "没有抢到锁";
            }
            // 添加锁成功
            // 从 redis 中获取库存
            String stock = operations.get("sk:0008");
            int mount = stock == null ? 0 : Integer.parseInt(stock);
            if (mount > 0){
                // 修改库存后再写会 redis
                operations.set("sk:0008", String.valueOf(--mount));
                return "秒杀成功，库存剩余：" + mount + "台";
            }
        }finally {
            // 只有添加锁的客户端才能释放锁
            if (clientId.equals(operations.get(REDIS_LOCK))){
                // 释放锁
                stringRedisTemplate.delete(REDIS_LOCK);
            }
        }
        return "抱歉，您没有抢到";
    }

    /**
     * 利用 reids 添加分布式锁
     * @return
     */
    @GetMapping("/sk3")
    public String seckillHandler3(){
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();

        try {
            // 添加锁,同时添加过期时间
            Boolean lockOk = operations.setIfAbsent(REDIS_LOCK, "I'm a lock", 30, TimeUnit.SECONDS);
            if (Boolean.FALSE.equals(lockOk)){
                return "没有抢到锁";
            }
            // 添加锁成功
            // 从 redis 中获取库存
            String stock = operations.get("sk:0008");
            int mount = stock == null ? 0 : Integer.parseInt(stock);
            if (mount > 0){
                // 修改库存后再写会 redis
                operations.set("sk:0008", String.valueOf(--mount));
                return "秒杀成功，库存剩余：" + mount + "台";
            }
        }finally {
            // 释放锁
            stringRedisTemplate.delete(REDIS_LOCK);
        }
        return "抱歉，您没有抢到";
    }

    /**
     * 添加同步锁， 抗高并发 ----> 问题: 同步锁，无法在多台服务器(集群)中保障
     * @return
     */
    @GetMapping("/sk2")
    public String seckillHandler2(){
        String result = "抱歉，您没有抢到";
        // todo 同步锁，无法在多台服务器(集群)中保障
        synchronized (this){
            // 从 redis 中获取库存
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            String stock = operations.get("sk:0008");
            int mount = stock == null ? 0 : Integer.parseInt(stock);
            if (mount > 0){
                // 修改库存后再写会 redis
                operations.set("sk:0008", String.valueOf(--mount));
                result = "秒杀成功，库存剩余：" + mount + "台";
                System.out.println(result);
            }
        }
        return result + "，当前服务器端口：" + serverPort;
    }

    /**
     * 初始化 代码
     * @return
     */
    @GetMapping("/sk1")
    public String seckillHandler1(){
        // 从 redis 中获取库存
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        String stock = operations.get("sk:0008");
        int mount = stock == null ? 0 : Integer.parseInt(stock);
        if (mount > 0){
            // 修改库存后再写会 redis
            operations.set("sk:0008", String.valueOf(--mount));
            return "秒杀成功，库存剩余：" + mount + "台";
        }
        return "抱歉，您没有抢到";
    }



}
